package sm.codec;

import org.apache.mina.core.buffer.IoBuffer;

import sm.codec.api.Codec;
import sm.codec.exception.DecodeException;
import sm.codec.exception.EncodeException;
import sm.def.Type;

public class NumberCodec implements Codec {

	protected Object decode(IoBuffer buffer, int type) throws DecodeException {
		byte numberByte = buffer.get();
		int intNumber = numberByte & 0x0F;
		Object number = null;
		if (intNumber > 8) {
			if (intNumber == 9) {
				byte byte1 = buffer.get();
				number = new Double(byte1 & 0xFF);
			} else if (intNumber == 10) {
				byte byte1 = buffer.get();
				byte byte2 = buffer.get();
				number = new Double((byte1 & 0xFF) * 256 + (byte2 & 0xFF));
			} else if (intNumber == 11) {
				byte byte1 = buffer.get();
				byte byte2 = buffer.get();
				byte byte3 = buffer.get();
				number = new Double((byte1 & 0xFF) * 65536 + (byte2 & 0xFF)
						* 256 + (byte3 & 0xFF));
			} else if (intNumber == 12) {
				number = new Double(buffer.getInt());
			} else if (intNumber == 13) {
				byte byte1 = buffer.get();
				int length = byte1 & 0xFF;
				if (length == 4) {
					number = new Double(buffer.getFloat());
				} else if (length == 8) {
					number = buffer.getDouble();
				} else {
					throw new DecodeException("The float type " + numberByte
							+ " with length " + length + " is not supported",
							buffer);
				}
			} else if (intNumber == 14) {
				byte byte1 = buffer.get();
				byte byte2 = buffer.get();
				int length = (byte1 & 0xFF) * 256 + (byte2 & 0xFF);
				throw new DecodeException("The float type " + numberByte
						+ " with length " + length + " is not supported",
						buffer);
			} else if (intNumber == 15) {
				number = Type.getType(type).getNullValue();
			}
		} else {
			number = new Double(intNumber);
		}

		return number;
	}

	@Override
	public Object decode(IoBuffer buffer) throws DecodeException {
		return decode(buffer, Type.NUMBER_TYPE);
	}

	protected void encode(IoBuffer buffer, Object number, int type)
			throws EncodeException {
		byte typeByte = (byte) (type << 4);
		if (number == null || number == Type.getType(type).getNullValue()) {
			byte numberByte = (byte) (0x0F | typeByte);
			buffer.put(numberByte);
		} else {
			Double doubleNumber = (Double) number;
			double doubleValue = doubleNumber.doubleValue();
			int intValue = doubleNumber.intValue();
			if (intValue == doubleNumber) {
				if (intValue < 0) {
					byte numberByte = (byte) (0x0C | typeByte);
					buffer.put(numberByte);
					buffer.putInt(intValue);
				} else {
					if (intValue > 7) { // 0x19 0x08
						if (intValue < 256) {
							byte numberByte = (byte) (0x09 | typeByte);
							buffer.put(numberByte);
							byte byte1 = (byte) (intValue & 0xFF);
							buffer.put(byte1);
						} else if (intValue < 65536) {
							byte numberByte = (byte) (0x0A | typeByte);
							buffer.put(numberByte);
							byte byte1 = (byte) ((intValue & 0xFF00) >>> 8);
							byte byte2 = (byte) (intValue & 0xFF);
							buffer.put(byte1);
							buffer.put(byte2);
						} else if (intValue < 16777216) {
							byte numberByte = (byte) (0x0B | typeByte);
							buffer.put(numberByte);
							byte byte1 = (byte) ((intValue & 0xFF0000) >>> 16);
							byte byte2 = (byte) ((intValue & 0xFF00) >>> 8);
							byte byte3 = (byte) (intValue & 0xFF);
							buffer.put(byte1);
							buffer.put(byte2);
							buffer.put(byte3);
						} else {
							byte numberByte = (byte) (0x0C | typeByte);
							buffer.put(numberByte);
							buffer.putInt(intValue);
						}
					} else {
						byte numberByte = (byte) (typeByte | intValue);
						buffer.put(numberByte);
					}
				}
			} else {
				byte numberByte = (byte) (0x0D | typeByte);
				buffer.put(numberByte);
				buffer.put((byte) 8);
				buffer.putDouble(doubleValue);
			}
		}
	}

	@Override
	public void encode(IoBuffer buffer, Object number) throws EncodeException {
		encode(buffer, number, Type.NUMBER_TYPE);
	}
}
